#include "Camera.h"
#include <glm/gtc/type_ptr.hpp>
#include "DataFilter.h"
#include "ZhicheEntity.h"

Camera::Camera() {
	auto filter = DataFilter::get();
	m_camMatricesSignal.connect([filter](const Mat& viewMatrix, const Mat& projMatrix) {
		filter->slotCameraMatrix(viewMatrix, projMatrix);
	});

	recalculateIfNeeded();
}

void Camera::connectCamChangeSignal(void* zhicheEntity) {
	auto zhiche = static_cast<ZhicheEntity*>(zhicheEntity);// specified for ZhicheEntity for sure!
	m_camChangeSignal.connect([zhiche] { zhiche->slotCameraChange(); });
}

void Camera::recalculateIfNeeded() {
	if (m_isViewMatrixDirty)
		viewMatrix();
	if (m_isProjectionMatrixDirty)
		projectionMatrix();
}

/**************************** V P matrix *************************/
const Mat& Camera::viewMatrix() {
	if (Proj::isSSQK())
		return orthoViewMatrix();
	if (m_isViewMatrixDirty) {
		m_isViewMatrixDirty = false;
		auto cameraRotX = m_pitchAngle;
		auto transMat = glm::translate(Mat_Identity, -m_position);
		auto rotXMat = glm::rotate(Mat_Identity, -cameraRotX, Vec3(1, 0, 0));
		auto rotYMat = glm::rotate(Mat_Identity, -m_yawAngle, Vec3(0, 1, 0));
		m_viewMatrix = rotXMat * rotYMat * transMat;
		m_viewMatrix *= glm::scale(Mat_Identity, Vec3(m_viewScale));
		if (m_isProjectionMatrixDirty)
			projectionMatrix();
		else
			m_camMatricesSignal(m_viewMatrix, m_projectionMatrix);// emit signal
	}
	return m_viewMatrix;
}

const Mat& Camera::projectionMatrix() {
	if (Proj::isSSQK())
		return orthoProjectionMatrix();
	if (m_isProjectionMatrixDirty) {
		m_isProjectionMatrixDirty = false;
		const auto Aspect = float(W3D) / H3D;
		const auto NearPlane = Config::Camera_NearPlane;
		const auto FarPlane = Config::Camera_FarPlane;
		m_projectionMatrix = glm::perspective(m_fov, Aspect, NearPlane, FarPlane);
		m_camMatricesSignal(m_viewMatrix, m_projectionMatrix);// emit signal
	}
	return m_projectionMatrix;
}

/*************************** Project: SSQK (Orthogonal Projection)*******************************/
const Mat& Camera::orthoViewMatrix() {/**@note if using glm::lookAt, upVector=(0,0,-1)*/
	if (m_isViewMatrixDirty) {
		m_isViewMatrixDirty = false;
		auto transMat = glm::translate(Mat_Identity, -m_position);
		auto rotXMat = glm::rotate(Mat_Identity, -m_pitchAngle, Vec3(1, 0, 0));
		auto rotYMat = glm::rotate(Mat_Identity, -m_yawAngle, Vec3(0, 1, 0));
		m_viewMatrix = rotXMat * rotYMat * transMat;
		m_viewMatrix *= glm::scale(Mat_Identity, Vec3(m_viewScale));
		if (m_isProjectionMatrixDirty)
			orthoProjectionMatrix();
		else
			m_camMatricesSignal(m_viewMatrix, m_projectionMatrix);// emit signal
	}
	return m_viewMatrix;
}

const Mat& Camera::orthoProjectionMatrix() {
	if (m_isProjectionMatrixDirty) {
		m_isProjectionMatrixDirty = false;
		const float AspectRatio = float(W3D) / H3D;
		const float Height = 10.0f;// fixed logical height
		float width = Height * AspectRatio;// proportional scaling
		m_projectionMatrix = glm::ortho(
			-width/2, width/2,   // X-axis
			-Height/2, Height/2, // Z-axis
			-10.0f, 10.0f        // Y-axis
		);
		m_camMatricesSignal(m_viewMatrix, m_projectionMatrix);// emit signal
	}
	return m_projectionMatrix;
}